﻿using Devonline.AspNetCore;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;

namespace Devonline.Identity;

/// <summary>
/// identity HttpClient service
/// </summary>
public class IdentityService
{
    private readonly ILogger<IdentityService> _logger;
    private readonly IDistributedCache _cache;
    private readonly AppSetting _setting;
    private readonly HttpClient _httpClient;
    private readonly HttpContext _httpContext;
    public IdentityService(
        ILogger<IdentityService> logger,
        AppSetting setting,
        HttpClient httpClient,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor
        )
    {
        _logger = logger;
        _setting = setting;
        _cache = cache;
        _httpClient = httpClient;
        _httpContext = httpContextAccessor.HttpContext;
        _httpClient.BaseAddress = new Uri(_setting.Identity.Authority);
        //_httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, ContentType.Json);
        _httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, _setting.Identity.ClientId);
    }

    /// <summary>
    /// 登录
    /// </summary>
    /// <returns></returns>
    public async Task<UserInfo> LoginAsync(UserLoginModel model)
    {
        _logger.LogDebug($"user {model.UserName} will login from Identity Service!");
        var token = await GetIdentityTokenAsync(model);
        ArgumentNullException.ThrowIfNull(token);

        _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(AppSettings.DEFAULT_AUTHENTICATION_SCHEME, token.AccessToken);
        var identityUserInfo = await GetIdentityUserInfoAsync(model.UserName);
        ArgumentNullException.ThrowIfNull(identityUserInfo);

        var userInfo = await GetUserInfoAsync(model.UserName);
        userInfo.Token = token;

        _logger.LogInformation($"user {model.UserName} login success and get the token and userinfo from Identity Service!");
        return userInfo;
    }
    /// <summary>
    /// 退出登录
    /// </summary>
    /// <returns></returns>
    public async Task LogoutAsync()
    {
        _logger.LogDebug("user will logout from Identity Service!");
        var response = await _httpClient.GetAsync(_setting.UserInteraction.Logout);
        var result = response.IsSuccessStatusCode ? "success" : (await response.Content.ReadAsStringAsync());
        _logger.LogDebug($"user logout from Identity Service: {result}");
    }

    /// <summary>
    /// 获取用户信息
    /// </summary>
    /// <param name="userName"></param>
    /// <returns></returns>
    public async Task<UserInfo> GetUserInfoAsync(string userName = default)
    {
        userName ??= _httpContext.GetUserName();
        _logger.LogDebug($"user {userName} will get user info from Identity Service for current system");
        var userInfo = await _cache.GetValueAsync<UserInfo>(AppSettings.CACHE_USER + userName);
        ArgumentNullException.ThrowIfNull(userInfo);

        string system = _httpContext.Request.GetRequestOrigin();
        var resources = userInfo.ResourceTree.Where(x => x.ResourceType == ResourceType.System && (x.Alias == system || x.Content == system)).ToList();
        if (!resources.Any() && userInfo.ResourceTree.Any(x => x.Children.Any(a => a.ResourceType == ResourceType.System && (a.Alias == system || a.Content == system))))
        {
            resources = userInfo.ResourceTree.FirstOrDefault(x => x.Children.Any(a => a.ResourceType == ResourceType.System && (a.Alias == system || a.Content == system))).Children.Where(x => x.ResourceType == ResourceType.System && (x.Alias == system || x.Content == system)).ToList();
        }

        if (resources.Any() && resources.Count == 1)
        {
            resources = resources.FirstOrDefault().Children.Where(x => x.ResourceType == ResourceType.Module || x.ResourceType == ResourceType.Page).ToList();
        }

        if (resources.Any())
        {
            foreach (var resource in resources)
            {
                CleanResourceTree(resource);
            }
        }

        _logger.LogInformation($"user {userName} get user info from Identity Service for currcnt system success!");
        return new UserInfo
        {
            User = userInfo.User,
            Resources = resources
        };
    }
    /// <summary>
    /// 用户是否有访问资源的权限
    /// </summary>
    /// <param name="resource"></param>
    /// <returns></returns>
    public async Task<bool> UserHasPermissionAsync(string resource)
    {
        var userName = _httpContext.GetUserName();
        _logger.LogDebug($"user {userName} will get user permission from Identity Service for: {resource}!");
        var response = await _httpClient.GetAsync($"{_setting.UserInteraction.UserPermission}?resource={resource}");
        var result = await response.Content.ReadAsStringAsync();
        _logger.LogDebug($"user {userName} get user permission from Identity Service for resource {resource}: {result}");
        return response.IsSuccessStatusCode ? result.To<bool>() : false;
    }

    /// <summary>
    /// 获取 access token
    /// </summary>
    /// <param name="model"></param>
    /// <returns></returns>
    private async Task<IdentityToken> GetIdentityTokenAsync(UserLoginModel model)
    {
        _logger.LogDebug($"user {model.UserName} will get token from Identity Service!");
        var dic = new Dictionary<string, string>
        {
            { _setting.Identity.GetColumnName(nameof(_setting.Identity.ClientId)), _setting.Identity.ClientId },
            { _setting.Identity.GetColumnName(nameof(_setting.Identity.ClientSecret)), _setting.Identity.ClientSecret },
            { _setting.Identity.GetColumnName(nameof(_setting.Identity.GrantType)), _setting.Identity.GrantType },
            { model.GetColumnName(nameof(model.UserName)), model.UserName },
            { model.GetColumnName(nameof(model.Password)), model.Password }
        };
        var httpContent = new FormUrlEncodedContent(dic);
        httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(ContentType.FormUrlencoded);
        var response = await _httpClient.PostAsync(_setting.Identity.Token, httpContent);
        var result = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            var token = result.ToJsonObject<IdentityToken>();
            if (token is not null)
            {
                _logger.LogInformation($"user {model.UserName} get access token success: {result}");
                return token;
            }
        }

        _logger.LogDebug($"user {model.UserName} get token from Identity Service failed: {result}");
        return default;
    }
    /// <summary>
    /// get identity userinfo from identity service
    /// </summary>
    /// <param name="userName"></param>
    /// <returns></returns>
    private async Task<IdentityUserInfo> GetIdentityUserInfoAsync(string userName)
    {
        _logger.LogDebug($"user {userName} will get userinfo from Identity Service!");
        var response = await _httpClient.GetAsync(_setting.Identity.UserInfo);
        var result = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            var userInfo = result.ToJsonObject<IdentityUserInfo>();
            if (userInfo is not null)
            {
                _logger.LogInformation($"user {userName} get userinfo from Identity Service success: {result}");
                return userInfo;
            }
        }

        _logger.LogDebug($"user {userName} get userinfo from Identity Service failed: {result}");
        return default;
    }
    /// <summary>
    /// 重构用户权限树
    /// </summary>
    /// <param name="resource"></param>
    private void CleanResourceTree(ResourceViewModel resource)
    {
        if (resource.Children.Any())
        {
            foreach (var child in resource.Children)
            {
                if (child.ResourceType == ResourceType.Module || child.ResourceType == ResourceType.Page)
                {
                    CleanResourceTree(child);
                }
            }

            resource.Children = resource.Children.Where(x => x.ResourceType == ResourceType.Module || x.ResourceType == ResourceType.Page).ToList();
        }
    }
}